package com.evildog.workspace.security.jwt.util;


import cn.hutool.core.lang.Assert;
import com.evildog.workspace.common.exception.BadRequestException;
import com.evildog.workspace.common.util.WsAssert;
import com.evildog.workspace.security.jwt.constant.TokenConstant;
import com.evildog.workspace.security.jwt.core.Token;
import com.evildog.workspace.security.jwt.core.TokenUserInfo;
import com.evildog.workspace.security.jwt.enums.SecurityJwtCustomExceptionDesc;
import com.evildog.workspace.security.jwt.prop.JwtProperties;
import io.jsonwebtoken.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JwtUtil
 */
@Slf4j
@RequiredArgsConstructor
public class JwtUtil {

    private final JwtProperties jwtProperties;

    /**
     * 获取去除头后真正的token信息
     * 认证头格式 bearer{auth}
     *
     * @param auth token
     * @return String
     */
    public String getToken(String auth) {
        String bearer = jwtProperties.getBearer();
        int authLength = bearer.length();
        if ((auth != null) && (auth.length() > authLength)) {
            String headStr = auth.substring(0, authLength).toLowerCase();
            if (headStr.compareTo(bearer) == 0) {
                auth = auth.substring(authLength);
                return auth;
            }
        }
        return null;
    }

    /**
     * 解析token
     *
     * @param token token串
     * @return Claims
     */
    public Claims parseJWT(String token) {
        String signKey = jwtProperties.getSignKey();
        WsAssert.hasText(signKey, SecurityJwtCustomExceptionDesc.SIGN_KEY_IS_BLANK);

        String base64Security = Base64.getEncoder().encodeToString(signKey.getBytes(StandardCharsets.UTF_8));
        try {
            return Jwts.parser()
                    .setSigningKey(Base64.getDecoder().decode(base64Security))
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException ex) {
            log.error("JWT已过期: token: {}", token);
            log.error("", ex);
            return null;
        } catch (Exception ex) {
            throw new BadRequestException(SecurityJwtCustomExceptionDesc.JWT_PARSE_ERROR);
        }
    }


    /**
     * 创建令牌
     *
     * @param tokenType tokenType
     * @param params    需要添加的额外参数
     * @return jwt
     */
    public Token createJWT(Map<String, Object> params, String tokenType) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        //生成签名密钥
        String signKey = jwtProperties.getSignKey();
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        String base64Security = Base64.getEncoder().encodeToString(signKey.getBytes(StandardCharsets.UTF_8));
        byte[] apiKeySecretBytes = Base64.getDecoder().decode(base64Security);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

        //添加构成JWT的类
        JwtBuilder builder = Jwts.builder().setHeaderParam("type", "JsonWebToken")
                .setIssuer(jwtProperties.getIssuer())
                .setAudience(jwtProperties.getAudience())
                .setSubject(jwtProperties.getSubject())
                .signWith(signatureAlgorithm, signingKey);


        //添加Token过期时间
        long expireMillis;
        if (tokenType.equals(TokenConstant.ACCESS_TOKEN)) {
            expireMillis = TokenConstant.ACCESS_TOKEN_EXPIRES_SECOND; // 2小时
        } else if (tokenType.equals(TokenConstant.REFRESH_TOKEN)) {
            expireMillis = TokenConstant.REFRESH_TOKEN_EXPIRES_SECOND; // 半天
        } else {
            expireMillis = TokenConstant.ACCESS_TOKEN_EXPIRES_SECOND; // 2小时
        }

        long expMillis = nowMillis + expireMillis;
        Date exp = new Date(expMillis);
        builder.setExpiration(exp).setNotBefore(now);

        params.put(TokenConstant.TOKEN_TYPE, tokenType);

        //设置JWT参数
        params.forEach(builder::claim);

        // 组装Token信息
        return new Token().setToken(builder.compact()).setExpireSecond((int) expireMillis / 1000);
    }

    public Token createAccessTokenByUserInfo(TokenUserInfo tokenUserInfo) {
        Map<String, Object> params = new HashMap<>();

        Long userId = tokenUserInfo.getUserId();

        Assert.notNull(userId, "userId required");

        params.put(TokenConstant.USER_ID, userId);
        params.put(TokenConstant.AVATAR, tokenUserInfo.getAvatar());
        params.put(TokenConstant.NICKNAME, tokenUserInfo.getNickname());

        return createJWT(params, TokenConstant.ACCESS_TOKEN);
    }

    public Token createRefreshTokenByUserInfo(TokenUserInfo tokenUserInfo) {
        Map<String, Object> params = new HashMap<>(16);
        params.put(TokenConstant.USER_ID, tokenUserInfo.getUserId());
        return createJWT(params, TokenConstant.REFRESH_TOKEN);
    }

}
